/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 *
 * $Id: IOData.cpp,v 1.4 2009-05-13 07:18:17 amaula Exp $
 *
 * \file
 * \brief Implementation for IO interface data encoder/decoder.
 * \author Matthieu Myrsky <matthieu.myrsky@tkk.fi>
 */
#include "IOData.hpp"
#include "binbag.h"
#include "owndebug.h"
//*****************************************************************************
using namespace MaCI::IO;
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
CIOData::CIOData(void):
  iValid(false),
  iTimestampPtr(NULL),
  iCommandPtr(NULL),
  iPinState(NULL),
  iBlockState(NULL),
  iAnalogVoltage(NULL),
  iBlockMask(NULL)
{

}
//*****************************************************************************

CIOData::~CIOData(void)
{

}
//*****************************************************************************

CIOData::CIOData(const CIOData &aIOData):
  iValid(false),
  iTimestampPtr(NULL),
  iCommandPtr(NULL),
  iPinState(NULL),
  iBlockState(NULL),
  iAnalogVoltage(NULL),
  iBlockMask(NULL)
{
  *this = aIOData;
}
//*****************************************************************************
     
CIOData &CIOData::operator=(const CIOData &aIOData)
{
  // Destroy the internal binbag (unconditional)
  DestroyInternalBinBag();
  
  // Create an internal BinBag instance
  if (CreateInternalBinBag(aIOData.iBinBagContainer) == false) {
    dPrint(4,"WARNING: Failed to create internal binbag for object %p from object %p",
           this, &aIOData);
  }
  
  // Do the decode on the buffer; it sets pointers and
  if (DecodeFrom() == false) {
    dPrint(4,"WARNING: Failed to Decode IOData even though assignment operator used?");
  }

  return *this; 
}
//*****************************************************************************

bool CIOData::DecodeFrom(gim::binbag::CBinBag *aBinBag)
{
  bool result = true;
  const gim::binbag::TBinBlob *b = NULL;
  int previous_type = -1;
  
  // Store pointer
  if (aBinBag != NULL)  
    iBinBagContainer = aBinBag;


  // Parse
  if (iBinBagContainer && iBinBagContainer->Rewind()) {
    while( (b = iBinBagContainer->GetNextBinary()) && result ) {
      switch(b->type) {
      case KTypeTimestamp:
	if (iTimestampPtr == NULL) {
	  iTimestampPtr = reinterpret_cast<Common::TTimestamp *>((char *)b->data);
	} else result = false;
	break;
      

      case KTypeCommand:
	if(iCommandPtr == NULL){
	  iCommandPtr = reinterpret_cast< TCommand *>((char *)b->data);
	
	}else result = false;
	break;


      case KTypeDigitalPin:
	if(iPinState == NULL){
	  iPinState = reinterpret_cast< TDigitalPinState *>((char *)b->data);
	  
	}else result = false;
	break;
	
      case KTypeDigitalBlock:
	if(iBlockState == NULL){
	  iBlockState = reinterpret_cast< TDigitalBlockState *>((char *)b->data);

	}else result = false;
	break;

      case KTypeAnalogVoltage:
	if(iAnalogVoltage == NULL){
	  iAnalogVoltage = reinterpret_cast< TAnalogVoltage *>((char *)b->data);

	}else result = false;
	break;

      case KTypeDigitalBlockMask:
	if(iBlockMask == NULL){
	  iBlockMask = reinterpret_cast< TDigitalBlockMask *>((char *)b->data);

	}else result = false;
	break;
      default:
	// Unknown Type in BinBag. Do nothing. (Ignore for compability)
	break;
      }

      // Store previous type
      previous_type = b->type;
    }
  } else result = false;
  
  // Store the result
  SetReadOnlyContainer( (iValid = result) );
  
  return result;
  
  
}
//*****************************************************************************

void CIOData::Reset()
{
  CMaCIData::Reset();

  iValid = false;
  iTimestampPtr =NULL;
  iCommandPtr = NULL;
  iPinState = NULL;
  iBlockState =NULL;
  iAnalogVoltage = NULL;
  iBlockMask = NULL;
}
//*****************************************************************************

void CIOData::Print(const int level) const
{
  const gim::binbag::TBinBlob *b = NULL;
  // Parse
  if (iBinBagContainer && iBinBagContainer->Rewind()) {
    while( (b = iBinBagContainer->GetNextBinary())) {
      switch(b->type) {
      case KTypeTimestamp: {
	const Common::TTimestamp *ts = 
          reinterpret_cast<const Common::TTimestamp *>(b->data);
	dPrint(level,"TTimestamp : %d, %d", 
	       ts->timestamp_s,
	       ts->timestamp_us);
	break;
      }
	
      case KTypeCommand: {
	const TCommand *ts = 
          reinterpret_cast<const TCommand *>(b->data);
	dPrint(level,"TCommand: %d", 
	       ts->cmd);
	break;
      }
	
      case KTypeDigitalPin: {
	const TDigitalPinState *ts = 
          reinterpret_cast<const TDigitalPinState *>(b->data);
	dPrint(level,"TDigitalPinState: id:%d, state:%d", 
	       ts->id, ts->state);
	break;
      }
	
      case KTypeDigitalBlock:{
	const TDigitalBlockState *ts = 
          reinterpret_cast<const TDigitalBlockState *>(b->data);
	dPrint(level,"TDigitalBlockState: id:%d, state:%d", 
	       ts->id, ts->state);
	break;
      }

      case KTypeAnalogVoltage:{
	const TAnalogVoltage *ts = 
          reinterpret_cast<const TAnalogVoltage *>(b->data);
	dPrint(level,"TAnalogVoltage: channel:%d, voltage:%f", 
	       ts->channel, ts->voltage);
	break;

      }
      case KTypeDigitalBlockMask:{
	const TDigitalBlockMask *bm = 
	   reinterpret_cast<const TDigitalBlockMask *>(b->data);
	dPrint(level,"TDigitaBlocklMask: id:%d, state:%f, mask: %d", 
	       bm->state.id, bm->state.state,bm->mask);
	break;
      }
      default:
	// Unknown Type in BinBag. Do nothing.
	break;
      }
    }
  }
}
//*****************************************************************************

bool CIOData::SetTimestamp(const Common::TTimestamp &aStamp)
{
  bool result = false;
  if (!IsReadOnlyContainer() && iTimestampPtr == NULL) {
    const gim::binbag::TBinBlob *b = 
      iBinBagContainer->AddBinary(KTypeTimestamp, 
                                  (const char *)&aStamp, sizeof(Common::TTimestamp));
    iTimestampPtr = reinterpret_cast<Common::TTimestamp *>((char*)b->data);
    
    // Mark last element
    SetLastElementType( KTypeTimestamp);
    
    result = true;
  }
  return result;
}
//*****************************************************************************

bool CIOData::SetDigitalPinState(const TDigitalPinState &aState)
{
  bool result = false;
  if (!IsReadOnlyContainer() && iPinState == NULL) {
    const gim::binbag::TBinBlob *b = 
      iBinBagContainer->AddBinary(KTypeDigitalPin, 
                                  (const char *)&aState, sizeof(TDigitalPinState));
    iPinState = reinterpret_cast<TDigitalPinState *>((char*)b->data);
    
    // Mark last element
    SetLastElementType( KTypeDigitalPin);
    
    result = true;
  }
  return result;

}
//*****************************************************************************

bool CIOData::SetDigitalBlockState(const TDigitalBlockState &aState)
{
  bool result = false;
  if (!IsReadOnlyContainer() && iBlockState == NULL) {
    const gim::binbag::TBinBlob *b = 
      iBinBagContainer->AddBinary(KTypeDigitalBlock, 
                                  (const char *)&aState, sizeof(TDigitalBlockState));
    iBlockState = reinterpret_cast<TDigitalBlockState *>((char*)b->data);
    
    // Mark last element
    SetLastElementType( KTypeDigitalBlock);
    
    result = true;
  }
  return result;

}
//*****************************************************************************

bool CIOData::SetAnalogVoltage(const TAnalogVoltage &aAnalogVoltage)
{
  bool result = false;
  if (!IsReadOnlyContainer() && iAnalogVoltage == NULL) {
    const gim::binbag::TBinBlob *b = 
      iBinBagContainer->AddBinary(KTypeAnalogVoltage, 
                                  (const char *)&aAnalogVoltage, sizeof(TAnalogVoltage));
    iAnalogVoltage = reinterpret_cast<TAnalogVoltage *>((char*)b->data);
    
    // Mark last element
    SetLastElementType( KTypeAnalogVoltage);
    
    result = true;
  }
  return result;
  
}
//*****************************************************************************

bool CIOData::SetDigitalBlockMask(const TDigitalBlockMask &aDigitalMask)
{
  bool result = false;
  if (!IsReadOnlyContainer() && iBlockMask == NULL) {
    const gim::binbag::TBinBlob *b = 
      iBinBagContainer->AddBinary(KTypeDigitalBlockMask, 
                                  (const char *)&aDigitalMask, sizeof(TDigitalBlockMask));
    iBlockMask = reinterpret_cast<TDigitalBlockMask *>((char*)b->data);
    
    // Mark last element
    SetLastElementType( KTypeDigitalBlockMask);
    
    result = true;
  }
  return result;
  
}
//*****************************************************************************

bool CIOData::SetCommand(const TCommand &aCmd)
{
  bool result = false;
  if (!IsReadOnlyContainer() && iCommandPtr == NULL) {
    const gim::binbag::TBinBlob *b = 
      iBinBagContainer->AddBinary(KTypeCommand, (const char *)&aCmd, sizeof(TCommand));
    iCommandPtr = reinterpret_cast<const TCommand *>(b->data);

    // Mark last element
    SetLastElementType( KTypeCommand);

    result = true;
  }
  return result;
}
//*****************************************************************************
const MaCI::Common::TTimestamp *CIOData::GetTimestamp(void) const 
{

  return iTimestampPtr;
}
//*****************************************************************************

const TDigitalPinState *CIOData::GetDigitalPinState(void) const 
{
  return iPinState;
}
//*****************************************************************************

const TDigitalBlockState *CIOData::GetDigitalBlockState(void) const 
{
  return iBlockState;
}
//*****************************************************************************

const TAnalogVoltage *CIOData::GetAnalogVoltage(void) const 
{
  return iAnalogVoltage;
}
//*****************************************************************************

const TCommand *CIOData::GetCommand(void) const
{
  return iCommandPtr;
}
//*****************************************************************************
const TDigitalBlockMask *CIOData::GetDigitalBlockMask(void) const
{
  return iBlockMask;

}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************


#ifdef COMPILE_IODATA_STANDALONE_MAIN

// Assert sequences verify that the container is working correctly
// (== The data read matches the data wrote in)
#include <assert.h>

int main(void)
{
  bool r;
  using namespace MaCI::IO;
  gim::binbag::CBinBag bb;
  CIOData A;
  CIOData B;

  printf("Carry out Encoding/Decoding test.\n");

  printf("Encoding...\n");
  // Do Encoding 
  r = A.EncodeTo(&bb);
  assert(r == true);

  printf("Encode: Timestamp (1, 2)\n");
  MaCI::Common::TTimestamp tt(1, 2);
  r = A.SetTimestamp(tt);
  assert(r == true);

  printf("Encode: SetDigitalPinState( 1, 1)\n");
  TDigitalPinState ps(1,1);
  r = A.SetDigitalPinState(ps);
  assert(r == true);

  printf("Encode: SetDigitalBlockState( 1, 4)\n");
  TDigitalBlockState bs(1,4);
  r = A.SetDigitalBlockState(bs);
  assert(r == true);
  
  printf("Encode: SetAnalogVoltage( 5, 3.9)\n");
  TAnalogVoltage av(5,3.9);
  r = A.SetAnalogVoltage(av);
  assert(r == true);

  printf("\nDecoding...\n");
  // Do Decoding
  B.DecodeFrom(&bb);
  printf("Decode: Timestamp = (%d, %d)\n", 
	 B.GetTimestamp()->timestamp_s, 
	 B.GetTimestamp()->timestamp_us);
  assert(B.GetTimestamp()->timestamp_s == 1);
  assert(B.GetTimestamp()->timestamp_us == 2);
  
  printf("Decode: GetDigitalPinState() = (%d, %d)\n",
	 B.GetDigitalPinState()->id,
	 B.GetDigitalPinState()->state);
  assert(B.GetDigitalPinState()->id == 1);
  assert(B.GetDigitalPinState()->state == 1);

  printf("Decode: GetDigitalBlockState() = (%d, %d)\n",
	 B.GetDigitalBlockState()->id,
	 B.GetDigitalBlockState()->state);
  assert(B.GetDigitalBlockState()->id == 1);
  assert(B.GetDigitalBlockState()->state == 4);
  

  printf("Decode: GetAnalogVoltage() = (%d, %f)\n",
	 B.GetAnalogVoltage()->channel,
	 B.GetAnalogVoltage()->voltage);
  assert(B.GetAnalogVoltage()->channel == 5);
  assert(B.GetAnalogVoltage()->voltage == (float)3.9);

 
  printf("\nCopying and Re-Decoding...\n");
  CIOData C(B);
  printf("Decode: Timestamp = (%d, %d)\n", 
	 C.GetTimestamp()->timestamp_s, 
	 C.GetTimestamp()->timestamp_us);
  assert(C.GetTimestamp()->timestamp_s == 1);
  assert(C.GetTimestamp()->timestamp_us == 2);
  
  printf("Decode: GetDigitalPinState() = (%d, %d)\n",
	 C.GetDigitalPinState()->id,
	 C.GetDigitalPinState()->state);
  assert(C.GetDigitalPinState()->id == 1);
  assert(C.GetDigitalPinState()->state == 1);

  printf("Decode: GetDigitalBlockState() = (%d, %d)\n",
	 C.GetDigitalBlockState()->id,
	 C.GetDigitalBlockState()->state);
  assert(C.GetDigitalBlockState()->id == 1);
  assert(C.GetDigitalBlockState()->state == 4);
  

  printf("Decode: GetAnalogVoltage() = (%d, %f)\n",
	 C.GetAnalogVoltage()->channel,
	 C.GetAnalogVoltage()->voltage);
  assert(C.GetAnalogVoltage()->channel == 5);
  assert(C.GetAnalogVoltage()->voltage == (float)3.9);
  

  printf("Test complete.\n");
  

}
#endif
